// javascript canvas testing playground

// ref:
// https://developer.mozilla.org/zh-CN/docs/Web/JavaScript
// https://www.bilibili.com/video/BV1Tu4y1479G/

// chrome javascript debug
// https://cloud.tencent.com/developer/article/2123190


// ref: 
// canvas stroke thick and fuzzy (multiple time, darker color)
// https://stackoverflow.com/questions/3991113/html5-canvas-stroke-thick-and-fuzzy?rq=4

const cvs = document.querySelector('canvas');
const ctx = cvs.getContext('2d');

// TODO use object-oriented
// now everything global (simplicity)

// newbie 3x3
// easy 6x6
// hard mode 8x8  (testing 10x8)
const col = 7;  // x
const row = 7;  // y
// stone = 'xy xy xy ...'
// const stone = '11'; // for newbie 3x3
// const stone = '01 11 21 20'; // for newbie 3x3, hori stone array
// const stone = '02 12 21 41 43 44 51 53'; // for 6x6
// 8x8 hard mode
// const stone = '03 10 12 14 22 25 27 33 36 41 45 47 56 62 63 65';
// 6x6 mid
// const stone = '01 21 22 30 33 41 43 44 51';
// 7x7 mid-high
const stone = '04 10 12 14 20 25 31 33 36 52 53 64 65';
// const stone = '';

// grid width and height
const GWIDTH = 50; // GW
const GHEIGHT = 50; // GH
const SX = 100;
const SY = 50;

const MAX_HAMMER = 9; 
const DEFAULT_HAMMER = 8;
const FONT_WIDTH = 36; // unify all fonts size are the same
const FONT = FONT_WIDTH + 'px courier';

// drawButton('RESTART', SX, SY+GHEIGHT*11, GWIDTH*5, GHEIGHT*2);
const BUT_X = SX;
const BUT_Y = SY + GHEIGHT * 10;
const BUT_WIDTH = GWIDTH * 5;
const BUT_HEIGHT = GHEIGHT * 1.5;
const BUT_Y2 = BUT_Y + GHEIGHT * 1.5;


// var? let?  init actor on bottom left
let actorX = 0;
let actorY = row-1;
let hammer = DEFAULT_HAMMER;  // may have more later? max 8

// for findRoute and dfs
let min_hammer = 999;
let min_path = []; // list of coordinates


const GRID = {
    LAND: 0,
    WALKED: 1,
    BROKEN: 2,  // stone already broken, treat as land
    STONE: 3,
    LAST: 3,  // always same as the last element
}

const FINISH = {
    PLAYING: 0, // not finish, can move, still have LAND
    WIN: 1,     // can or cannot move, no more LAND
    LOSE: 2,    // cannot move, still have LAND
}

// number assignment may change later
// do NOT start with 0
// used by drawRoute, findRoute()
//     // 1=North, 2=West, 3=South, 4=East
const DIR = {
    NORTH: 1,
    WEST: 2,
    SOUTH: 3,
    EAST: 4,
}

let board = [];
let route = [];

/**
 * 
 * @param {String} st 
 */
function parseStone(st) {
    let list = st.split(' '); // split by space
    for (let i=0; i<list.length; i++) {
        if (list[i]=='') {
            continue;
        }
        let xy = Number(parseInt(list[i], 10));
        let x = (xy / 10) | 0;
        let y = xy % 10;
        // console.log('parseStone: ' + x + ', ' + y);
        board[y][x] = GRID.STONE;
    }

}

// init board and route
function initBoard() {
    board = [];
    route = [];

    for (let y=0; y<row; y++) {
        board[y] = [];
        route[y] = [];
        for (let x=0; x<col; x++) {
            board[y][x] = GRID.LAND;
            route[y][x] = 0; // empty
        }
    }
    // actor always on the bottom left
    board[row-1][0] = GRID.WALKED; // init as walked

    parseStone(stone);
    actorY = row-1;
    actorX = 0;
    hammer = DEFAULT_HAMMER;

    // for testing
    /*
    route[2][0] = DIR.NORTH;
    route[1][0] = DIR.NORTH;
    route[0][0] = DIR.EAST;
    route[0][1] = DIR.EAST;
    route[0][2] = DIR.SOUTH;
    route[1][2] = DIR.SOUTH;
    route[2][2] = DIR.WEST;
    route[2][1] = DIR.WEST;
    */


}

function init() {
    
    // keep this for ref
    console.log('canvas w, h=' + cvs.width + ', ' + cvs.height);
    console.log('parent w, h=' + parent.width + ', ' + parent.height); // undefined
    console.log('window w, h=' + window.width + ', ' + window.height); // undefined
    console.log('client w, h=' + cvs.clientWidth + ', ' + cvs.clientHeight);
    // ctx.scale(2, 2); // for macbook retina? dimension x 2, not good
}

// keep for testing only
function draw() {
    ctx.strokeStyle = 'black';
    ctx.fillStyle = 'green';
    // ctx.beginPath();
    // ctx.moveTo(50, 50);
    // ctx.lineTo(100, 80);
    // ctx.closePath();
    ctx.fillRect(0, 0, 50, 50);
    ctx.stroke();  // paint it
}



function drawBoard() {
    let x, y, x1, x2, y1, y2;
    let cx, cy, radius;
    ctx.beginPath();
    ctx.strokeStyle = 'brown'; // how about using RGB ?

    // draw the border line
    x1 = SX;
    x2 = SX + col * GWIDTH;
    for (let r=0; r<=row; r++) {
        y = SY + r*GHEIGHT;
        // ctx.beginPath();
        ctx.strokeStyle = 'brown';
        ctx.moveTo(x1, y);
        ctx.lineTo(x2, y);
        // ctx.closePath();
        // ctx.stroke();
    }

    y1 = SY;
    y2 = SY + row * GHEIGHT;
    for (let c=0; c<=col; c++) {
        x = SX + c*GWIDTH;
        ctx.moveTo(x, y1);
        ctx.lineTo(x, y2);

    }
    ctx.closePath();
    ctx.stroke();

    for (y=0; y<row; y++) {
        for (x=0; x<col; x++) {
            switch(board[y][x]) {
                case GRID.LAND:
                    ctx.fillStyle = 'green';
                    ctx.fillRect(SX+x*GWIDTH+1, SY+y*GHEIGHT+1, GWIDTH-1, GHEIGHT-1);
                    break;

                case GRID.WALKED:
                    ctx.fillStyle = 'white';
                    ctx.fillRect(SX+x*GWIDTH+1, SY+y*GHEIGHT+1, GWIDTH-1, GHEIGHT-1);
                    break;

                case GRID.BROKEN:
                    ctx.strokeStyle = 'silver';
                    ctx.fillStyle = 'silver';
                    cx = SX + x*GWIDTH + GWIDTH/2;
                    cy = SY + y*GHEIGHT + GHEIGHT/2;
                    // draw a broken face
                    // 1.5PI topmost (NORTH)
                    // 2PI rightmost (EAST)
                    radius = GWIDTH / 2 - 4;
                    ctx.beginPath();
                    ctx.arc(cx, cy, radius, 1.5*Math.PI, 2*Math.PI, true);
                    // draw the L shape (must do, else not close)
                    ctx.lineTo(cx, cy);
                    ctx.lineTo(cx, cy-radius);
                    ctx.closePath();
                    ctx.fill();
                    break;
    
                case GRID.STONE:
                    ctx.strokeStyle = 'silver';
                    ctx.fillStyle = 'silver';
                    cx = SX + x*GWIDTH + GWIDTH/2;
                    cy = SY + y*GHEIGHT + GHEIGHT/2;
                    radius = GWIDTH / 2 - 4;
                    ctx.beginPath();
                    ctx.arc(cx, cy, radius, 0, 2*Math.PI, false);
                    ctx.closePath();
                    ctx.fill();
                    break;

                default:   // broken 
                    // TODO
                    ctx.fillStyle = 'grey';
                    ctx.fillRect(SX+x*GWIDTH+2, SY+y*GHEIGHT+2, GWIDTH-4, GHEIGHT-4);
                    break;

            }
        }
    }

    // draw the grid number, for debug

    drawGridNumber();
    drawActor(actorX, actorY);
    drawHammer(hammer);

    drawRoute(); // order is important
}


function drawRoute() {
    let cx, cy;
    let arrow = 6;
    let margin = 2;
    ctx.strokeStyle = 'red';
    ctx.fillStyle = 'red';
    for (let y=0; y<row; y++) {
        for (let x=0; x<col; x++) {
            switch(route[y][x]) {
                case DIR.SOUTH:
                cx = SX + x*GWIDTH + GWIDTH/2;
                cy = SY + y*GHEIGHT + margin;
                ctx.beginPath();
                // draw arrow first
                ctx.moveTo(cx - arrow, cy - arrow + GHEIGHT - margin * 2);
                ctx.lineTo(cx + arrow, cy - arrow + GHEIGHT - margin * 2);
                ctx.lineTo(cx , cy + GHEIGHT - margin * 2);
                ctx.lineTo(cx - arrow, cy - arrow + GHEIGHT - margin * 2);
                ctx.closePath();
                ctx.stroke();
                ctx.fill();

                ctx.beginPath();
                ctx.fillRect(cx - 1, cy, 3, GHEIGHT - margin * 2 - arrow);
                ctx.closePath();
                ctx.fill();
                break;

                case DIR.NORTH:
                cx = SX + x*GWIDTH + GWIDTH/2;
                cy = SY + y*GHEIGHT + margin;
                ctx.beginPath();
                // draw arrow first
                ctx.moveTo(cx, cy);
                ctx.lineTo(cx - arrow, cy + arrow);
                ctx.lineTo(cx + arrow, cy + arrow);
                ctx.closePath();
                ctx.stroke();
                ctx.fill();

                ctx.beginPath();
                ctx.fillRect(cx - 1, cy + arrow+1, 3, GHEIGHT - margin * 2 - arrow);
                ctx.closePath();
                ctx.fill();
                break;


                case DIR.EAST: // south sample, TODO more direction
                cx = SX + x*GWIDTH + margin;
                cy = SY + y*GHEIGHT + GHEIGHT/2;
                // draw line thickness=3
                ctx.beginPath();
                ctx.fillRect(cx, cy - 1, GWIDTH - margin * 2 - arrow, 3);
                ctx.closePath();
                ctx.fill();

                // draw arrow
                ctx.beginPath();
                cx = cx + GWIDTH - margin * 2;
                ctx.moveTo(cx, cy);
                ctx.lineTo(cx - arrow, cy - arrow);
                ctx.lineTo(cx - arrow, cy + arrow);
                ctx.closePath();
                ctx.stroke();
                ctx.fill();
                break;


                case DIR.WEST: 
                cx = SX + x*GWIDTH + margin;
                cy = SY + y*GHEIGHT + GHEIGHT/2;
                // draw arrow
                ctx.beginPath();
                ctx.moveTo(cx, cy);
                ctx.lineTo(cx + arrow, cy - arrow);
                ctx.lineTo(cx + arrow, cy + arrow);
                ctx.closePath();
                ctx.stroke();
                ctx.fill();

                // draw line thickness=3
                ctx.beginPath();
                ctx.fillRect(cx + arrow+1, cy - 1, GWIDTH - margin * 2 - arrow, 3);
                ctx.closePath();
                ctx.fill();

                break;

            }
        }
    }
}


function drawGridNumber() {
    let x, y;
    ctx.font = FONT;
    ctx.fillStyle = 'black';
    // draw the horizontal number
    y = SY - GHEIGHT + GHEIGHT/2;
    for (gx=0; gx<col; gx++) {
        x = SX + (GWIDTH - FONT_WIDTH) / 2 + gx * GWIDTH;
        ctx.fillText(gx, x, y);
    }

    x = SX - GWIDTH + (GWIDTH-FONT_WIDTH)/2;
    for (gy=0; gy<row; gy++) {
        y = SY + GHEIGHT*gy + GHEIGHT*0.7; // /2 not good looking
        ctx.fillText(gy, x, y);
    }
}


/**
 * 
 * @param {Number} gx  integer grid x
 * @param {Number} gy  integer grid y
 */
function drawRedBox(gx, gy) {

    let x = pixelX(gx) + 4;
    let y = pixelY(gy) + 4;
    ctx.strokeStyle = 'red';
    ctx.strokeRect(x, y, GWIDTH-8, GHEIGHT-8);
    ctx.stroke();
}


/**
 * red, green, blue handy function
 * @param {Number} r  0-255 integer
 * @param {Number} g 
 * @param {Number} b 
 * @returns 
 */
function rgb(r, g, b){
    return "rgb("+r+","+g+","+b+")";
}


/**
 * 
 * @param {String} label 
 * @param {Number} x 
 * @param {Number} y 
 * @param {Number} width
 * @param {Number} height
 */
function drawButton(label, x, y, width, height) {
    ctx.fillStyle = rgb(188, 188, 188);
    ctx.fillRect(x, y, width, height);

    ctx.fillStyle = rgb(128, 128, 128);
    ctx.fillRect(x+2, y+2, width-4, height-4);

    ctx.fillStyle = 'black';
    ctx.font = FONT;
    ctx.fillText(label, x+30, y+FONT_WIDTH*1.5);
}


/**
 * 
 * gx, gy must be integer
 * when h is undefined, only check range of gx, gy
 * @param {Number} gx 
 * @param {Number} gy 
 * @param {Number} h 
 * @returns 
 */
function checkMove(gx, gy, h) {
    if (gx < 0 || gy < 0) {
        return false;
    }

    if (gx >= col || gy >= row) {
        return false;
    }
    // only check out of range gx, gy
    if (h==undefined) {
        return true;
    }

    let l = board[gy][gx];
    // TODO check h>0 ???
    if (l==GRID.LAND) {
        return true;
    }
    if (l==GRID.STONE && h>0) {
        return true;
    }
    return false;
}

/**
 * 
 * @param {Array} bb 
 * @param {Number} gx 
 * @param {Number} gy 
 * @param {Number} h 
 * @returns 
 */
function checkMoveBB(bb, gx, gy, h) {
    if (gx < 0 || gy < 0) {
        return false;
    }

    if (gx >= col || gy >= row) {
        return false;
    }
    // only check out of range gx, gy
    if (h==undefined) {
        return true;
    }

    let l = bb[gy][gx];
    // TODO check h>0 ???
    if (l==GRID.LAND) {
        return true;
    }
    if (l==GRID.STONE && h>0) {
        return true;
    }
    return false;
}


// draw a human actor 
function drawActor(gx, gy) {
    let x = SX + gx*GWIDTH;
    let y = SY + gy*GHEIGHT;
    let cx = x + GWIDTH/2;
    let cy = y + GHEIGHT/3;
    let radius = GHEIGHT / 5;
    ctx.strokeStyle = 'blue';
    ctx.fillStyle = 'blue';
    ctx.beginPath();
    ctx.arc(cx, cy, radius, 0, 2*Math.PI);
    ctx.closePath();
    ctx.stroke();
    ctx.fill();

    ctx.beginPath();
    ctx.strokeStyle = 'blue';
    ctx.fillStyle = 'blue';
    ctx.moveTo(cx, cy + radius);
    ctx.lineTo(cx - GWIDTH/4, cy + radius *3);
    ctx.lineTo(cx + GWIDTH/4, cy + radius *3);
    ctx.lineTo(cx, cy + radius);
    ctx.closePath();
    ctx.stroke();
    ctx.fill();

    // check 4 directions and 
    // draw redbox for legitimate move
    // north
    if (gy > 0) {
        if (board[gy-1][gx]==GRID.LAND) {
            drawRedBox(gx, gy-1);
        }
        // TODO check stone and hammer
    }
    // south
    if (gy < col-1) {
        if (board[gy+1][gx]==GRID.LAND) {
            drawRedBox(gx, gy+1);
        }
    }
    // west
    if (gx > 0) {
        if (board[gy][gx-1]==GRID.LAND) {
            drawRedBox(gx-1, gy);
        }
    }
    // east
    if (gx < row -1) {
        if (board[gy][gx+1]==GRID.LAND) {
            drawRedBox(gx+1, gy);
        }
    }

}


/**
 * 
 * @param {Number} h 
 */
function drawHammer(h) {

    let x = SX + (col + 2) * GWIDTH;
    let y = SY + (MAX_HAMMER) * GHEIGHT;

    ctx.fillStyle = 'green'; // TODO BG_COLOR constant
    ctx.fillRect(x, y - MAX_HAMMER * GHEIGHT, GWIDTH, GHEIGHT*MAX_HAMMER);
    y = y - GHEIGHT / 4;
    x = x + (GWIDTH - FONT_WIDTH);

    ctx.fillStyle = 'black';
    // ctx.font = '32px courier';
    ctx.font = FONT;
    for (let gy=0; gy<h; gy++) {
        ctx.fillText('T', x, y - gy*GHEIGHT);
    }

}


/**
 * 
 * @param {String} msg 
 */
function drawStatus(msg) {

    let x = SX ;
    let y = SY + 10 * GHEIGHT;

    // for testing: red background, blue text
    ctx.fillStyle = 'white';
    ctx.fillRect(x, y-GHEIGHT, GWIDTH*10, GHEIGHT);

    ctx.fillStyle = 'blue';
    ctx.font = FONT;
    ctx.fillText('Status123: ' + msg, x + GWIDTH/5, y - GHEIGHT/5);
    ctx.fill();

}


// TOP left pixel x for grid gx
function pixelX(gx) {
let x = SX + gx * GWIDTH;
    return x;
}


function pixelY(gy) {
    let y = SY + gy * GHEIGHT;
    return y;
}


function gridX(x) {
    let gx = (x - SX - 1) / GWIDTH;
    if (gx < 0) {
        // WARN later
        console.log('WARN gridX return -1');
        return -1;
    }
    // convert to integer
    console.log(gx);
    return gx | 0;
}


function gridY(y) {
    let gy = (y - SY - 1) / GHEIGHT;
    if (gy < 0) {
        // WARN later
        console.log('WARN gridY return -1');
        return -1;
    }
    // | 0  float convert to integer
    console.log(gy);
    return gy | 0;
}



/**
 * @return 0=nothing, 1=win, 2=lose
 * depends on actorX, actorY, board[][]
 */
function checkWin() {
    let land, gx, gy;
    land = 0;
    for (let y=0; y<row; y++) {
        for (let x=0; x<col; x++) {
            if (board[y][x] == GRID.LAND) 
                land++;
        }
    }
    // all land gone, means win
    if (0==land) {
        return FINISH.WIN; // win
    }

    gx = actorX;
    gy = actorY;
    if (checkMove(gx-1, gy, hammer)) {
        return FINISH.PLAYING;
    }
    if (checkMove(gx+1, gy, hammer)) {
        return FINISH.PLAYING;
    }
    if (checkMove(gx, gy-1, hammer)) {
        return FINISH.PLAYING;
    }
    if (checkMove(gx, gy+1, hammer)) {
        return FINISH.PLAYING;
    }

    // assert(land > 0);
    return FINISH.LOSE;
}


/**
 * depth first search algorithm 
 * to find a route which cover all lands and
 * use minimum hammer
 * access global: min_hammer, min_path
 * ending criteria: l <= 0
 * @param {*} bb  - deep clone of board
 * @param {*} path - array of coordinates e.g. [ [x,y], [x2, y2] , ..]
 * @param {Number} h  - no. of hammer used >=0
 * @param {Number} l  - land remain, init all land
 */
function dfs(bb, path, h, l) {
    if (h >= 10 || h >= min_hammer) {
      // use too many hammers
      return ;
    }
    if (l < 0) {
        // negative is impossible indeed
        console.log('BUGBUG dfs l < 0 : ' + l);
        return;
    }
    if (l == 0) {
        console.log('END hammer=' + h);
        console.dir(path);
        if (h < min_hammer) {
            min_hammer = h;
            min_path = structuredClone(path);
        }
        // implicit: else not an optimal path
        return;
    }

    let gx, gy;
    let pos;
    pos = path[path.length-1];
    gx = pos[0];
    gy = pos[1];
    // console.log('pos=' + pos + ' h=' + h + ' l=' + l);

    let tx, ty;
    let old_land;
    // 4 directions
    tx = gx + 1;
    ty = gy;
    if (checkMoveBB(bb, tx, ty, 1)) {
        // console.log('EAST ' + tx + ', ' + ty);
        old_land = bb[ty][tx];
        bb[ty][tx] = GRID.WALKED;
        path[path.length] = [tx, ty];
        if (old_land==GRID.STONE) {
            dfs(bb, path, h+1, l);
        } else {
            dfs(bb, path, h, l-1);
        }
        bb[ty][tx] = old_land; // resume
        path.length = path.length - 1;
    }


    tx = gx - 1;
    ty = gy;
    if (checkMoveBB(bb, tx, ty, 1)) {
        // console.log('WEST ' + tx + ', ' + ty);
        old_land = bb[ty][tx];
        bb[ty][tx] = GRID.WALKED;
        path[path.length] = [tx, ty];
        if (old_land==GRID.STONE) {
            dfs(bb, path, h+1, l);
        } else {
            dfs(bb, path, h, l-1);
        }
        bb[ty][tx] = old_land;
        path.length = path.length - 1; // reduce by 1
    }

    tx = gx;
    ty = gy + 1;
    if (checkMoveBB(bb, tx, ty, 1)) {
        // console.log('SOUTH ' + tx + ', ' + ty);
        old_land = bb[ty][tx];
        bb[ty][tx] = GRID.WALKED;
        path[path.length] = [tx, ty];
        if (old_land==GRID.STONE) {
            dfs(bb, path, h+1, l);
        } else {
            // assert bb[ty][tx]==GRID.LAND
            dfs(bb, path, h, l-1);
        }
        bb[ty][tx] = old_land; // resume
        path.length = path.length - 1;
    }

    tx = gx;
    ty = gy - 1 ;
    if (checkMoveBB(bb, tx, ty, 1)) {
        // console.log('NORTH ' + tx + ', ' + ty);
        old_land = bb[ty][tx];
        bb[ty][tx] = GRID.WALKED;
        path[path.length] = [tx, ty];
        if (old_land==GRID.STONE) {
            dfs(bb, path, h+1, l);
        } else {
            dfs(bb, path, h, l-1);
        }
        bb[ty][tx] = old_land; // resume
        path.length = path.length - 1;
    }
    return;
}

/**
 * 
 * @param {Array} pos  pos[0]=gx  pos[1]=gy
 * @returns {Number} dir  [1, 4] for North South East West
 */
function pos2dir(pos1, pos2) {
    let gx1, gx2, gy1, gy2;
    let adx, ady, dx, dy, dir;
    gx1 = pos1[0];
    gy1 = pos1[1];
    gx2 = pos2[0];
    gy2 = pos2[1];

    // horizontal and vertical difference
    
    // dx = 1  dy = 0
    // adx=1  ady=0
    dx = gx2 - gx1;
    dy = gy2 - gy1;
    adx = Math.abs(dx);
    ady = Math.abs(dy);

    // dy (= [-1, 1], dy+1 => 0, 2  North, South
    // dx (= [-1, 1], dx+2 => 1, 3  West, East
    // afterwards, +1 to avoid using zero
    // 1=North, 2=West, 3=South, 4=East
    dir = 1 + (dy+1) * ady + (dx + 2) * adx;

    return dir;
}


function findRoute() {
    min_hammer = 999;
    min_path = [];
    let land = 0;
    let path = [];
    let gx, gy;
    let bb;

    bb = structuredClone(board);
    bb[row-1][0] = GRID.WALKED;
    for (let y=0; y<row; y++) {
        for (let x=0; x<col; x++) {
            if (bb[y][x]==GRID.LAND) {
                land++;
            }
        }
    }
    console.log('total_land=' + land);
    // core logic
    path[0] = [0, row-1];
    dfs(bb, path, 0, land);
    
    console.log('min_hammer=' + min_hammer);
    console.dir(min_path);

    let lastPos = min_path[0];
    let dir ;
    for (let i=1; i<min_path.length; i++) {
        gx = lastPos[0];
        gy = lastPos[1];
        dir = pos2dir(lastPos, min_path[i]);
        route[gy][gx] = dir; // later NESW
        // console.log('dir=' + dir);
        lastPos = min_path[i];
    }
    // fill-in the last one, same as second last
    gx = lastPos[0];
    gy = lastPos[1];
    route[gy][gx] = dir; // later NESW
    // last arrow is not necessary, for cosmetic only

    drawStatus('min_hammer='+ min_hammer);
}

/**
 *
 * Use this to let VSCode understand param type 
 * @param {MousEvent} event 
 */
function onClick(event) {

    // check restart button first
    // use offsetX is the most accurate
    // pageX does have some offset from mouse hotspot?
    // ref: https://medium.com/@greenduckstudiogames/the-javascript-mouse-event-mystery-clientx-pagex-offsetx-explained-eccd8231fc81
    let x = event.offsetX;
    let y = event.offsetY;
    let gx, gy;
    
    // console.log('onClick x, y=', x, y);
    if (x>=BUT_X && x<=BUT_X+BUT_WIDTH && y>=BUT_Y && y<=BUT_Y+BUT_HEIGHT) {
        // restart
        initBoard();
        drawBoard();
        drawStatus('Start');
        console.log('RESTARTing');
        return;
    }
    if (x>=BUT_X && x<=BUT_X+BUT_WIDTH && y>=BUT_Y2 && y<=BUT_Y2+BUT_HEIGHT) {
        // route button
        console.log('ROUTING');
        findRoute();
        drawBoard();
        drawRoute();        
        return;
    }

    if (checkWin() != FINISH.PLAYING) {
        return;
    }
    console.log('x===' + x);
    gx = gridX(x);
    gy = gridY(y);
    if (false == checkMove(gx, gy, undefined)) {
        return ; // early exit
    }
    l = board[gy][gx];
    // console.log('onClick: ' + event.clientX + ', ' + event.clientY + ' button:' + event.button 
    //     + ' altKey:' + event.altKey + ' gx,gy=' + gx + ', ' + gy + ' land:' + l);

    if (true==event.altKey) {
        l ++;
        if (l > GRID.LAST) { 
            l = GRID.LAND; // 0
        }
        board[gy][gx] = l;
        drawBoard();
        return; // early exit
    }

    if (true==event.shiftKey) {
        win = checkWin();
        drawStatus('win='+win);
        hammer = hammer - 1;
        if (hammer < 0) {
            hammer = 0;
        }
        console.log('hammer=' + hammer);
        drawBoard();
        return; // early exit
    }


    // check if the mouse click is 1 grid next to the actor
    let diff = Math.abs(actorX - gx) + Math.abs(actorY - gy);
    if (diff != 1) {
        return;
    }
    // ok, assume diff==1 now
    // meaning: actor is next to gx, gy


    let breaking = false;
    if (l==GRID.STONE) {
        if (hammer <= 0) {
            console.log('NO more hammer');
            drawStatus('No hammer');
            return;
        }
        hammer = hammer - 1;
        breaking = true;

    } else if (l!=GRID.LAND) {
        // else if (l==GRID.WALKED)
        // this is implicit "walked" land
        console.log('not walkable land');
        drawStatus('Cannot walk');
        // TODO hammer + GRID.STONE
        return;
    }

    // order is important!!! new board[y][x] is walked
    actorX = gx;
    actorY = gy;
    board[gy][gx] = GRID.WALKED;
    drawBoard();
    if (breaking) {
        drawStatus('Breaking');
    } else {
        drawStatus('Walking');
    }

    let win = checkWin();
    switch(win) {
        case FINISH.PLAYING: // no win, no lose, continue, nothing
        break;

        case FINISH.WIN: // win
        // drawStatus('Win! GOOD! \u263A');
        drawStatus('Win! GOOD! \u{1F603}');
        break;

        case FINISH.LOSE: // lose, no more move
        // crying face, using UTF-16 style
        // https://www.compart.com/en/unicode/U+1F62D
        // drawStatus('Lose \uD83D\uDE2D'); // work!
        drawStatus('Lose! \u{1F62D}')
        break;
    }
    
}


function initMouseEvent() {
    // cvs event is ok, no need to have window event (same?)
    cvs.addEventListener("click", onClick);
}


init();
initBoard();
drawBoard();
// win = checkWin();
drawStatus('Start');
drawButton('RESTART', BUT_X, BUT_Y, BUT_WIDTH, BUT_HEIGHT);
drawButton('Route', BUT_X, BUT_Y2, BUT_WIDTH, BUT_HEIGHT);

// drawActor(5, 5);  // for testing
initMouseEvent();

